#ifndef LOG_H
#define LOG_H

#include <corecrt_io.h>
#include <direct.h>
#include <windows.h>

#include <algorithm>
#include <string>
#include <iostream>
#include <format>
#include <mutex>

#define LOG_THROW(excp) throw(excp)

#define VMGT_EVENT_LOG_SOURCE "VMGT Error Reporting"

#define LOG_FOLDER_SEPS "\\/"
constexpr static const char FOLDER_SPES[] = LOG_FOLDER_SEPS;

#define LOG_LEVEL_DEBUG  0
#define LOG_LEVEL_INFO   1
#define LOG_LEVEL_WARN   2
#define LOG_LEVEL_ERROR  3
#define LOG_LEVEL_OFF    4
#ifndef LOG_ACTIVE_LEVEL
#define LOG_ACTIVE_LEVEL LOG_LEVEL_INFO
#endif

constexpr static const char* LOG_SHORT_LEVEL_NAMES[] = { "D", "I", "W", "E", "O" };

#ifndef LOG_CALL
#define LOG_CALL(level, msg, ...) do {                                         \
    if (level >= LOG_ACTIVE_LEVEL) {                                           \
        Log::Instance().LogMsg(level, __FILE__, __LINE__, msg, ##__VA_ARGS__); \
    }                                                                          \
} while (0)
#endif

#define LOGD(msg, ...) LOG_CALL(LOG_LEVEL_DEBUG, msg, ##__VA_ARGS__)
#define LOGI(msg, ...) LOG_CALL(LOG_LEVEL_INFO, msg, ##__VA_ARGS__)
#define LOGW(msg, ...) LOG_CALL(LOG_LEVEL_WARN, msg, ##__VA_ARGS__)
#define LOGE(msg, ...) LOG_CALL(LOG_LEVEL_ERROR, msg, ##__VA_ARGS__)

constexpr static const WCHAR* HIDDEN_SENSITIVE_DATA = L"<HIDDEN-DATA>";

#ifdef PRINT_SENSITIVE_DATA
#define GET_PRINTABLE_STRING(data) data
#else
#define GET_PRINTABLE_STRING(data) HIDDEN_SENSITIVE_DATA
#endif

void WideStrToUTF8(const std::wstring& wstr, std::string& target, BOOL includeTerminateNullChar = FALSE);

// Log exception
class LogException : public std::exception {
public:
    explicit LogException(std::string msg);
    LogException(const std::string& msg, int lastErrno);
    const char* what() const noexcept override;

private:
    std::string m_msg;
};

class EventLog {
public:
    EventLog(const std::string& source, DWORD eventId = 0)
        : m_source(source), m_eventId(eventId) { }
    ~EventLog();

    void WriteToEventLog(const std::string& msg, UINT level);

private:
    HANDLE GetEventLogHandle();
    WORD GetEventType(UINT level);

    std::mutex m_mutex;
    HANDLE m_hEventLog{nullptr};
    std::string m_source;
    DWORD m_eventId;
};

class FileMgr {
public:
    FileMgr() : m_eventLog(VMGT_EVENT_LOG_SOURCE) {};
    ~FileMgr();
    BOOL Open(const std::string& fileName, BOOL truncate = FALSE);
    BOOL Reopen(BOOL truncate);
    BOOL Flush();
    BOOL Sync();
    void Close();
    BOOL Write(const std::string& buf);
    BOOL Size(size_t& size);
    const std::string& FileName() const;
    std::tuple<std::string, std::string> SplitByExtension(const std::string& fileName);
    void WriteToEventLog(const std::string& msg, UINT level);

private:
    const UINT m_openTries = 5;
    const UINT m_openInterval = 10;
    std::FILE* m_fd{ nullptr };
    std::string m_fileName;
    EventLog m_eventLog;
};

class Log {
public:
    static Log& Instance()
    {
        static Log instance;
        return instance;
    }
    Log() = default;
    ~Log() = default;
    DWORD Init(std::string fileName, size_t maxSize, size_t backupFiles);

    template<typename... Args>
    void LogMsg(UINT level, const char* fileNameIn, int lineIn, const std::string& msg, Args&&... args)
    {
        LogToFile(level, fileNameIn, lineIn, std::vformat(msg, std::make_format_args(args...)));
    }

    template<typename... Args>
    void LogMsg(UINT level, const char* fileNameIn, int lineIn, const std::wstring& msg, Args&&... args)
    {
        LogToFile(level, fileNameIn, lineIn, std::vformat(msg, std::make_wformat_args(args...)));
    }

private:
    std::string CalcFileName(const std::string& fileName, size_t index);
    BOOL RotateFile();
    BOOL RenameFile(const std::string& srcFileName, const std::string& targetFileName);
    void LogToFile(UINT level, const char* fileNameIn, int lineIn, const std::string& msg);
    void LogToFile(UINT level, const char* fileNameIn, int lineIn, const std::wstring& msg);
    std::string m_fileName;
    size_t m_maxSize = 0;
    size_t m_maxBackupFiles = 0;
    size_t m_currentSize = 0;
    std::mutex m_mutex;
    BOOL m_bInitialised = FALSE;
    BOOL m_enableLog = TRUE;
    FileMgr m_fileMgr;
};

#endif // LOG_H
